package log;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map.Entry;

public class LogManager {
	
	protected String logFileName;
	protected String logName; 
	
	protected LinkedHashMap<String, String> logs = new LinkedHashMap<String, String>();
	protected LinkedHashMap<String, String> previousLine = new LinkedHashMap<String, String>();
	
	protected HashSet<String> priorityValues = new HashSet<String>();
	
	protected BufferedWriter logWriter;
	
	protected boolean verbal = false;
	
	protected boolean log = true;
	
	@SuppressWarnings("hiding")
	public LogManager(String logName, String logFileName, boolean verbal, boolean _log, String... keys) {
		
		this.logName = logName;
		this.logFileName = logName;
		this.verbal = verbal;
		this.log = _log;
		
		if (!log) {
			return;
		}
		
		/*
		 * Checking that the required folders exist
		 */
		
		String[] path = logFileName.split(File.separator);
		String currentPath = ".";
		
		for (int i = 0 ; i < path.length - 1 ; i++) {
			currentPath += File.separatorChar + path[i];
		}
		
		File d = new File(currentPath);
		System.out.println("Directory " + currentPath + " " + (d.mkdirs()?"":"un") + "succesfully created.");
		
		File f = new File(logFileName);
		try {
			System.out.println("File " + logFileName + " " + (f.createNewFile()?"":"un") + "succesfully created.");
		} catch (IOException e1) {
			e1.printStackTrace();
		}
		
		/*
		 * Initializing the keys
		 */
		
		for (String k : keys) {
			logs.put(k, null);
		}
		
		/*
		 * Writing the headers, i.e. the keys on top of the table
		 */
		
		try {
			logWriter = new BufferedWriter(new FileWriter(f));
			
			for (String key : keys) {
				logWriter.write(key);
				logWriter.write(',');
			}
			logWriter.write('\n');
			
			logWriter.flush();
			
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}
	
	/**
	 * Method to be called for each logged data.
	 * Does not actually write in the destination file, just associates a key with a value. 
	 * The value corresponding to the key can be overwritten later.
	 * 
	 * @param key
	 * @param data
	 * @param keepUntilNextWrite If true, this value will not be replaced by a new one until next write
	 */
	public synchronized void logValue(String key, String data, boolean keepUntilNextWrite) {
		
		if (!log) {
			return;
		}
		
		if (verbal && logs.get(key) != null) {
			// System.err.println(logName + ": WARNING: there's already a value at the key \"" + key + "\" : \"" + logs.get(key) + "\" becomes \"" + data + "\"");
		}
		
		if (verbal && !logs.containsKey(key)) {
			System.err.println(logName + ": WARNING: the key \"" + key + "\" does not exist in this log.");
			return;
		}
		
		if (keepUntilNextWrite || !priorityValues.contains(key)) {
			
			if (data == null) {
				logs.put(key, "");
			} else {
				logs.put(key, data);
			}
			
			if (keepUntilNextWrite) {
				priorityValues.add(key);
			}
			
		} else if (verbal) {
			System.err.println(logName + ": WARNING: previous value must be kept.");
		}
		
	}
	
	public void logValue(String key, String data) {
		logValue(key, data, false);
	}
	
	/**
	 * Writes the current data line.
	 * @param flush			Forces the flush of the writer
	 * @param eraseOlder	If true, all values are set to null after writing
	 */
	public synchronized void writeLogLine(boolean flush, boolean eraseOlder) {
		
		if (!log) {
			return;
		}
		
		if (verbal && logs.isEmpty()) {
			System.err.println(logName + ": WARNING: Log is empty");
			return;
		}
		
		/*
		 * Checking and indicating the missing fields
		 */
		
		StringBuffer missings = new StringBuffer();
		boolean allNull = true;
		
		for (Entry<String, String> entry : logs.entrySet()) {
			if (entry.getValue() == null) {
				missings.append('[');
				missings.append(entry.getKey());
				missings.append(']');
				missings.append(' ');
			} else {
				allNull = false;
			}
		}
		
		if (verbal) { 
			if (allNull) {
				System.err.println(logName + ": WARNING: All fields are empty");
				return;
			}
			
			if (missings.length() > 0) {
				System.err.println(logName + ": WARNING: the fields " + missings + "are empty when writing the log line.");
			}
		}
		
		if ( filter() ) {
		
			/*
			 * Building the line
			 */
			
			StringBuffer line = new StringBuffer();
			
			for (Object o : logs.values()) {
				
				line.append(o);
				line.append(',');
				
			}
			
			line.append('\n');
			
			/*
			 * Writing the line
			 */
			
			try {
				
				logWriter.write(line.toString());
				
				if (flush) {
					logWriter.flush();
				}
				
			} catch (IOException e) {
				e.printStackTrace();
			}
			
			/*
			 * Copying the previous line values
			 */
			
			previousLine = new LinkedHashMap<String, String>(logs);
			
			/*
			 * Resetting the values
			 */
			
			if (eraseOlder) {
				for (String k : logs.keySet()) {
					
					logs.put(k, null);
					
				}
			}
			
			priorityValues.clear();
			
		} else if (verbal) {
			
			System.err.println(logName + ": Line filtered.");
			
		}
		
	}
	
	/**
	 * This method can be overwritten to implement a filter mechanism on what should be logged.
	 * @return	true if the current line is worth writing, false either.
	 */
	public boolean filter() {
		return log;
	}
	
	public void flush() {
		
		if (!log) {
			return;
		}
		
		try {
			logWriter.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void close() {
		
		if (!log) {
			return;
		}
		
		try {
			logWriter.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void setLog(boolean _log) {
		this.log = _log;
	}
	
	public String getCurrentValue(String key) {
		return logs.get(key);
	}
	
}
